﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using ICSharpCode.AvalonEdit;
using ICSharpCode.AvalonEdit.AddIn;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.SharpDevelop.Editor;
using Oxage.Common;
using Oxage.Controls.AvalonEditExtensions;
using Oxage.Models;
using Oxage.Services;

namespace Oxage.Views
{
	public partial class EditorView : UserControl
	{
		#region Constructor
		public EditorView()
		{
			InitializeComponent();
			InitializeAvalonEdit();
		}
		#endregion

		#region Dependency properties

		#region Document
		public DocumentViewModel Document
		{
			get { return (DocumentViewModel)GetValue(DocumentProperty); }
			set { SetValue(DocumentProperty, value); }
		}

		public static readonly DependencyProperty DocumentProperty = DependencyProperty.Register("Document", typeof(DocumentViewModel), typeof(EditorView), new PropertyMetadata(DocumentChanged));

		private static void DocumentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var view = d as EditorView;
			var document = e.NewValue as DocumentViewModel;
			if (view != null && document != null)
			{
				//Apply content from model to editor
				view.textEditor.Text = document.Content;

				//Save content from editor back to model
				view.textEditor.LostKeyboardFocus += (sender, args) =>
				{
					document.Content = view.textEditor.Text;
				};

				//Apply content from model to editor when model content changes
				document.PropertyChanged += (sender, args) =>
				{
					if (args.PropertyName == "Content")
					{
						view.textEditor.Text = document.Content;
					}
				};
			}
		}
		#endregion

		#region IsDebugging
		public bool IsDebugging
		{
			get { return (bool)GetValue(IsDebuggingProperty); }
			set { SetValue(IsDebuggingProperty, value); }
		}

		public static readonly DependencyProperty IsDebuggingProperty = DependencyProperty.Register("IsDebugging", typeof(bool), typeof(EditorView), new PropertyMetadata(IsDebuggingChanged));

		private static void IsDebuggingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
		}
		#endregion

		#region CurrentLineBookmark
		public int CurrentLineBookmark
		{
			get { return (int)GetValue(CurrentLineBookmarkProperty); }
			set { SetValue(CurrentLineBookmarkProperty, value); }
		}

		public static readonly DependencyProperty CurrentLineBookmarkProperty = DependencyProperty.Register("CurrentLineBookmark", typeof(int), typeof(EditorView), new PropertyMetadata(CurrentLineBookmarkChanged));

		private static void CurrentLineBookmarkChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var control = d as EditorView;
			if (control != null)
			{
				control.SetCurrentLineBookmark((int)e.NewValue);
			}
		}
		#endregion

		#region SyntaxHighlighter
		public ISyntaxHighlighter SyntaxHighlighter
		{
			get { return (ISyntaxHighlighter)GetValue(SyntaxHighlighterProperty); }
			set { SetValue(SyntaxHighlighterProperty, value); }
		}

		public static readonly DependencyProperty SyntaxHighlighterProperty = DependencyProperty.Register("SyntaxHighlighter", typeof(ISyntaxHighlighter), typeof(EditorView), new PropertyMetadata(SyntaxHighlighterChanged));

		private static void SyntaxHighlighterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			var control = d as EditorView;
			if (control != null)
			{
				var highlighter = e.NewValue as ISyntaxHighlighter;
				if (highlighter != null)
				{
					//Custom syntax highlighting
					var definition = highlighter.GetDefinition();
					control.textEditor.SyntaxHighlighting = definition;

					//HighlightingManager.Instance.RegisterHighlighting(highlighter.Name, highlighter.FileExtensions, definition);
				}
				else
				{
					//No syntax highlighting
					control.textEditor.SyntaxHighlighting = null;
				}
			}
		}
		#endregion

		#endregion

		#region Public properties
		public EditorViewModel ViewModel
		{
			get
			{
				return this.DataContext as EditorViewModel;
			}
		}

		public ICSharpCode.AvalonEdit.TextEditor TextEditor
		{
			get
			{
				//Defined in XAML
				return textEditor;
			}
		}

		public BookmarkColumnMargin BookmarkColumn
		{
			get
			{
				return this.bookmarkColumn;
			}
		}
		private BookmarkColumnMargin bookmarkColumn;

		public TextMarkerService TextMarkerService
		{
			get
			{
				return this.textMarkerService;
			}
		}
		private TextMarkerService textMarkerService;
		#endregion

		#region Protected methods
		protected void InitializeAvalonEdit()
		{
			//Resources
			// http://www.codeproject.com/KB/edit/AvalonEdit.aspx

			//Breakpoints
			// http://community.sharpdevelop.net/search/SearchResults.aspx?q=breakpoint&s=10
			// http://community.sharpdevelop.net/forums/p/7182/20404.aspx#20404

			try
			{
				//Initialize G code syntax highlighter
				this.SyntaxHighlighter = new GCodeSyntaxHighlighter();

				//Set this editor as active text editor
				TextEditorService.ActiveTextEditor = textEditor;

				//Set content
				this.Document = App.ViewModel.Document; //NOTE: Temporary workaround
				//textEditor.Text = (this.Document != null ? this.Document.Content : "");
				//textEditor.TextChanged += (sender, e) => this.Document.HasChanged = true;
				textEditor.TextChanged += (sender, e) => App.ViewModel.Document.HasChanged = true;

				//Bookmarks
				bookmarkColumn = new BookmarkColumnMargin();
				bookmarkColumn.ToggleBookmarkRequest += (sender, e) => ToggleBreakpoint(e.Result);
				textEditor.TextArea.TextView.Services.AddService(typeof(IBookmarkColumn), bookmarkColumn);
				textEditor.TextArea.LeftMargins.Insert(0, bookmarkColumn);

				//Text marker
				textMarkerService = new TextMarkerService(textEditor.TextArea.TextView);
				textEditor.TextArea.TextView.BackgroundRenderers.Add(textMarkerService);
				textEditor.TextArea.TextView.LineTransformers.Add(textMarkerService);
				textEditor.TextArea.TextView.Services.AddService(typeof(ITextMarkerService), textMarkerService);

				//textEditor.TextArea.IndentationStrategy = "Smart"
				//AllowDrop="True"
				//IndentStyle="Smart"
				//IsIconBarVisible="False"
				//ShowTabs="False"
				//ShowSpaces="False"
				//UseAntiAliasFont="True" />

				this.IsCodeCompletionEnabled = true;

				//Redirect keyboard events
				//textEditor.KeyDown += (sender, e) => base.OnKeyDown(e);
				//textEditor.KeyUp += (sender, e) => base.OnKeyUp(e);

				//TODO: Find caret changed event or use timer
				//Update current line and column info
				textEditor.KeyUp += (sender, e) => UpdateInfo();
				textEditor.MouseUp += (sender, e) => UpdateInfo();

				textEditor.KeyDown += (sender, e) =>
				{
					int line = GetCaretLineNumber();

					var debugger = DebugService.CurrentProjectDebugger;

					switch (e.Key)
					{
						case Key.F9:
							ToggleBreakpoint(line);
							e.Handled = true;
							break;

						case Key.F10:
						case Key.F11:
							if (debugger != null)
							{
								debugger.StepInto();
								e.Handled = true;
							}
							break;

						case Key.Space:
							if ((Keyboard.Modifiers & ModifierKeys.Control) > 0)
							{
								if (this.IsCodeCompletionEnabled)
								{
									ShowCodeCompletionPopup();
									e.Handled = true;
								}
							}
							break;
					}

					if (!e.Handled && this.IsCodeCompletionOpened)
					{
						//Code completion popup
						e.Handled = CodeCompletionPopup.LastPopup.HandleKeyDown(e.Key);
					}
					
					if (!e.Handled)
					{
						//Global key handler
						e.Handled = KeyboardService.KeyDown(this, e.Key);
					}
				};
			}
			catch (Exception ex)
			{
				Logger.Log(ex);
			}
		}

		protected void UpdateInfo()
		{
			ViewModel.CaretLine = textEditor.TextArea.Caret.Line;
			ViewModel.CaretColumn = textEditor.TextArea.Caret.Column;
		}
		#endregion

		#region Code completion
		public bool IsCodeCompletionEnabled
		{
			get;
			set;
		}

		public bool IsCodeCompletionOpened
		{
			get
			{
				return CodeCompletionPopup.LastPopup != null ? CodeCompletionPopup.LastPopup.IsOpen : false;
			}
		}

		protected void ShowCodeCompletionPopup()
		{
			var items = GetCodeCompletionItems();
			var position = GetCodeCompletionPopupPosition();
			var popup = CodeCompletionPopup.ShowPopup(items, position, result =>
			{
				int offset = textEditor.TextArea.Caret.Offset;
				textEditor.Text.Insert(offset, result.Item.Summary);
			});
		}

		protected IEnumerable<ICodeCompletionItem> GetCodeCompletionItems()
		{
			yield return new CodeCompletionItem("G00", "Linear movement");
			yield return new CodeCompletionItem("G01", "Linear movement");
			yield return new SnippetCodeCompletionItem("arc", "Arc radius");
		}

		protected Point GetCodeCompletionPopupPosition()
		{
			//Upper left point of text editor (without line number and bookmark column)
			Point editorPoint = textEditor.TextArea.TextView.PointToScreen(new Point(0, 0));

			//Find caret position relative to editor upper left point
			TextViewPosition position = new TextViewPosition(textEditor.TextArea.Caret.Line, textEditor.TextArea.Caret.Column, textEditor.TextArea.Caret.VisualColumn);
			Point caretPoint = textEditor.TextArea.TextView.GetVisualPosition(position, VisualYPosition.TextTop);
			//Point caretPoint = textEditor.TextArea.TextView.GetVisualLine(textEditor.TextArea.Caret.Line).GetVisualPosition(textEditor.TextArea.Caret.VisualColumn, VisualYPosition.TextTop);

			//Additional offset to move popup below the letter
			Point offsetPoint = new Point(0, textEditor.FontSize * 1.2);

			//Return absolute popup position
			return new Point(editorPoint.X + caretPoint.X + offsetPoint.X, editorPoint.Y + caretPoint.Y + offsetPoint.Y);
		}
		#endregion

		#region Public methods
		public void ToggleBreakpoint(int lineNumber)
		{
			var bookmarks =
				from x in bookmarkColumn.Bookmarks
				where x.LineNumber == lineNumber && x is BreakpointBookmark
				select x;

			var bookmark = bookmarks.FirstOrDefault() as BreakpointBookmark;
			if (bookmark == null)
			{
				//Check if breakpoint is allowed at that line (if not comment)
				var segment = textEditor.Document.GetLineByNumber(lineNumber);
				string text = textEditor.Document.GetText(segment);
				if (this.SyntaxHighlighter != null && !this.SyntaxHighlighter.IsBreakpointAllowed(text))
				{
					return;
				}

				//Add bookmark icon
				bookmark = new BreakpointBookmark(lineNumber);
				bookmarkColumn.Bookmarks.Add(bookmark);

				//TODO: Column and offset if line contains more executable segments and breakpoint should be set to a specific segment
				//new Location(column, line)

				//Add text marking
				var marker = textMarkerService.Create(segment.Offset, segment.Length);
				marker.BackgroundColor = Color.FromRgb(180, 38, 38);
				marker.ForegroundColor = Colors.White;
				bookmark.Marker = marker;
			}
			else
			{
				//Remove bookmark icon
				bookmarkColumn.Bookmarks.Remove(bookmark);

				//Remove text marking
				if (bookmark.Marker != null)
				{
					textMarkerService.Remove(bookmark.Marker);
				}
			}
		}

		public void DeleteAllBreakpoints()
		{
			var bookmarks =
				(from x in bookmarkColumn.Bookmarks
				 where x is BreakpointBookmark
				 select x as BreakpointBookmark).ToList();

			while (bookmarks.Count() > 0)
			{
				var bookmark = bookmarks[0];

				//Remove bookmark icon
				bookmarkColumn.Bookmarks.Remove(bookmark);

				//Remove text marking
				if (bookmark.Marker != null)
				{
					textMarkerService.Remove(bookmark.Marker);
				}
			}
		}

		public int GetCaretLineNumber()
		{
			return textEditor.TextArea.Caret.Line; //Current line number
		}

		protected void SetCurrentLineBookmark(int lineNumber)
		{
			var bookmarks =
				(from x in bookmarkColumn.Bookmarks
				 where x is CurrentLineBookmark
				 select x).ToList();

			while (bookmarks.Count > 0)
			{
				var bookmark = bookmarks[0] as CurrentLineBookmark;

				//Remove bookmark icon
				bookmarkColumn.Bookmarks.Remove(bookmark);

				//Remove text marking
				if (bookmark.Marker != null)
				{
					textMarkerService.Remove(bookmark.Marker);
				}

				//Remove from temporary list
				bookmarks.Remove(bookmark);
			}

			//Check if current line contains executable code
			var segment = textEditor.Document.GetLineByNumber(lineNumber);
			string text = textEditor.Document.GetText(segment);
			if (this.SyntaxHighlighter != null && !this.SyntaxHighlighter.IsBreakpointAllowed(text))
			{
				return;
			}

			//Add bookmark icon
			var b = new CurrentLineBookmark(lineNumber);
			bookmarkColumn.Bookmarks.Add(b);

			//Add text marking
			var marker = textMarkerService.Create(segment.Offset, segment.Length);
			marker.BackgroundColor = Color.FromRgb(38, 180, 38);
			marker.ForegroundColor = Colors.White;
			b.Marker = marker;
		}
		#endregion
	}
}
